#!/usr/bin/python
# coding=utf8
###############################################################################
#                                                                             #
#    Copyright 2007 Danil Dotsenko                                       #
#                                                                             #
#    This program is free software; you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation; either version 2 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program; if not, write to the Free Software              #
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA #
#                                                                             #
###############################################################################                                   #
# contacts: Danil Dotsenko, dd@accentsolution.com                           #

#from __future__ import division
import karambino
import re
import mimetypes
import time
import os

def debug():
	return False

###########################################################################
# applet settings go here, and are accessible from everywhere as global
class configClass:
	'''
	An all-encoupasing, application-wide configuration storage location.
	This will be shared even between concurrently running themes.
	This is the equivalent of root directory of Windows registry.
	'''
	def __init__(self):
		self.common={
			'name':'TileList Example',
			'version':"June, 2007",
			'author':"Danil (Suslik) Dotsenko (dd@accentsolution.com)",
			'cleanStart':False,
			'interval':10000,
			}
		self.lang = None
		self.objects = {}
		self.data = dataClass()
#
##########################################################

class dataClass:
	'''
	Both, storage and retrieval functions live here.
	A convenient place to have add() search() and other data helper things.
	'''
	# potentially, this can be a place where internal storage 
	# could be switched to external SQL-like storage for interoperabiliy reasons.
	
	def __init__(self):
		self.db = {}
		self.template = {
			'key':'', # must be unique. Usually the full URL of the resource.
			'objtype':'',
			'shortname':'',
			'plugin':'',
			'group':0, # open = 1, undecided =0 , blocked =-1
			'tags':[], # maybe migrate set's functionality to this later.
			'extpath':'',
			}

	def __len__(self):
		return len(self.db)
	
	def add(self, *args, **kw):
		'''
		Process the steps needed to add a data units to the db properly
		'''
		if not self.db.has_key(kw['key']):
			self.db[kw['key']] = self.template.copy()
		# 1. steal underpants
		self.db[kw['key']].update(kw)
	
	def remove(self, *args, **kw):
		'''
		Process the steps needed to remove a data units to the db properly
		'''
		try:
			del self.db[kw['key']]
		except:
			pass
	
	def get(self, *args, **kw):
		'''
		Pulls data out. Assumes AND among all operators.
		Returns a list of data object references.
		Acceptable keywords:
		dataset - should be a list with key names of data in self.data DB.
		set - should be an integer.
		tags - should be a list of strings. Ex: ['todo','personal']
		text - a string. Could be a sub-string in either long, or short name.
		'''
		# we look into kw. Do a search based on keys. See self.template for keys.
		#key:'', # must be unique. Usually the URL of the resource.
		#objtype:'',
		#shortname:'',
		#plugin:'',
		#group:0, # open = 1, undecided =0 , blocked =-1
		#tags:[], # maybe migrate set's functionality to this later.
		ret = []
		if 'key' in kw:
			# Looks up specific entry in the db, using the key field.
			# This is very stupid. Implement set(kw).intersection([key names]) later.
			if kw['key'] in [type(u''),type('')]: # string or unicode
				kw['key'] = [kw['key']] # we make it a list.
			for i in kw['key']:
				try:
					ret.append(self.db[i])
				except:
					pass
		else:
			# in future, implement OR. That is why I started using the sets.
			# they also help remove double found entries
			if 'dataset' in kw:
				keys = set(kw['dataset'])
			else:
				keys = set(self.db.keys())
			if 'set' in kw:
				keys = set([i for i in list(keys) if self.db[i]['set']==kw['set']])
			if 'tags' in kw:
				keys = set([i for i in list(keys) for j in kw['tags'] if self.db[i]['tags'].count(j)])
			if 'text' in kw:
				names = '\n'+'\n'.join([i+'\t'+self.db[i]["shortname"] for i in list(keys)])+'\n'
				r = re.findall('[^\n]*'+kw['text']+'[^\n]*',names)
				keys = set([i.split('\t',1)[0] for i in r])
			ret=[self.db[i] for i in list(keys)]
		return ret

def sortby(arglist, data):
	'''
	This is our-data-specific sorting fn. It sorts given data LIST based on keys.
	arglist is always [], even with 1 entry. Refers to self.remplate's keys.
	 this is designed for sorting based on multiple criteria.
	data is [] with {},{},{} inside.
	'''
	# typical keys for our data.
	#key:'', # must be unique. Usually the URL of the resource.
	#objtype:'',
	#shortname:'',
	#plugin:'',
	#group:0, # open = 1, undecided =0 , blocked =-1
	#tags:[], # maybe migrate set's functionality to this later.
	# at this time, we support only one sorting arg.
	ret = []
	if type(arglist)==type([]) and type(data)==type([]):
		agent = arglist[0]
		twinlist = [(i[agent].upper(),i) for i in data]
		twinlist.sort()
		#try:
			#reversed = kw[reversed]
		#except:
			#reversed = False
		#if reversed:
			#twinlist.reverse()
		while twinlist:
			ret.append(twinlist.pop(0)[1])
	else:
		ret = data
	return ret

def setupObjects(widget):
	global appConfig
	# these pertain to the tileList widget. Budget extra space for decor.
	w, h, th = 140, 4, 60 #width, # of tiles, height of one.
	karambino.resizeWidget(widget, w, 70) # the header is 70 pixels tall.
	# image = karambino.imageClass('images/green.png', 0,0,100,90,widget=widget)

	#########################
	# header, the target and the button.
	karambino.redrawstatus = False # wrapping the actions in blanket no-draw.
	header = karambino.imageClass('images/glassback.png', widget=widget)
	header.buttonup = karambino.imageClass('images/buttonup.png', parent=header)
	header.buttonup.move(0,-4, "s","s")
	header.buttonup.hide()
	header.buttonup.label = 'up' # will use for identification

	header.buttondown = karambino.imageClass('images/buttondown.png', parent=header)
	header.buttondown.move(0,-4, "s","s")
	header.buttondown.hide()
	header.buttondown.label = 'down'

	header.buttonlabel = karambino.textClass('0', w=60, h=25, parent=header)
	header.buttonlabel.move(0,0, "center","center") # center
	header.buttonlabel.hide()

	header.buttontop = karambino.imageClass('images/buttonbling.png', parent=header)
	header.buttontop.move(0,-4, "s","s")
	header.buttontop.hide()
	header.buttontop.busy = time.time()+0.5 # we may be using threads. This will help identify work-in-progress.

	header.target = karambino.imageClass('images/target.png', parent=header)
	header.target.move(0,0, "center","center")
	
	#header.test = karambino.fancyInputClass(x=0, y=0, w=70, h=20, parent=header)
	header.toolbar = karambino.imageClass('images/glassback.png', w=w-10, h=26, parent=header)
	header.toolbar.move(0,0,'s','s')
	
	
	listbox = karambino.tileListClass(parent=header)
	
	karambino.redrawstatus = True # end of wrapping the actions in blanket no-draw.

	#listbox = karambino.tileListClass(parent=header)
	header.listboxshow = True # will be a flag. Means the ball, not the drop is shown.

	def resizeApplet():
		'''
		This hand-calculates the dimensions of known things and resizes the applet
		using essenrially karamba.resizeWidget(widget, x,y)
		'''
		# we inherit all of the variables from the parent function.
		dl = appConfig.data.__len__()
		if h >= dl: # there is less or equal number of data than set up tiles.
			if header.toolbar.visible:
				header.toolbar.hide()
			newH = 70+dl*th*int(header.listboxshow) # header height + data length * tile height
		else: # there are more data pieces. Thus, we also show the toolbar
			if not header.toolbar.visible:
				header.toolbar.show()
			newH = 70+h*th*int(header.listboxshow)
		karambino.resizeWidget(widget, w, newH)
		listbox.size(w, newH-70)

	def greenButtonClick(*args,**kw):
		# this shows and hides the list.
		# this also flips the green button from ball to drop.

		# note, Even though the green ball is shown when list is hidden,
		# the only way to click it is to hover above it, and when
		# you hover, a drop is shown, as an indication of what WILL happen.
		# so, even though "drop" is an indicator of shown list, you klick on "drop" to show it.
		# hence....
		item = args[0]
		if item.label == 'down' and header.buttontop.busy < time.time(): # drop AND list is HIDDEN
			header.buttontop.busy =  time.time()+0.5
			# we need to show list and change to green ball ('up')
			# Help dependents, then flip yourself.
			header.listboxshow = True
			resizeApplet()
			header.buttondown.hide(redraw=False)
			header.buttonup.show()
		elif item.label == 'up' and header.buttontop.busy < time.time(): # green ball and list visible.
			header.buttontop.busy = time.time()+0.5
			# we need to hide the list and switch to green drop
			# i would expect to hide the list here... but, instead, we will just reduce the size of the widget.
			item.busy = True # guarding against other threads
			header.listboxshow = False
			resizeApplet()
			header.buttonup.hide(redraw=False)
			header.buttondown.show()
			# Help dependents, then flip yourself.
		header.buttontop.busy = False # guarding against other threads

	def greenButtonHover(*args, **kw):
		# this will be triggered by hover, which sends lotsa extra args.
		# direction is either True(1,'asdf') or False(0,''), that stands for MOUSE IN or MOUSE OUT
		for i in args:
			if i in [True, False]:
				direction = i
		if header.listboxshow == direction: # either TRue True, or False False. Too long to explain. Draw all choices on matrix and see.
			# hiding teh Drop, and showing the Ball.
			header.buttondown.hide(redraw=False)
			header.buttonup.show()
		else:
			header.buttonup.hide(redraw=False)
			header.buttondown.show()
		#print "mouse %s, header.listboxshow %s" %(direction, header.listboxshow)
		#print 'down, up visibility ', header.buttondown.visible, header.buttonup.visible

	def showButton(*args, **kw):
		# I don't know what will trigger us, so don't know which arg is ours.
		karambino.redrawstatus = False
		for i in args:
			if i in [True, False]:
				state = i
		if state: # i.e. True, show button
			if header.listboxshow:
				header.buttonup.hide()
				header.buttondown.show()
			else:
				header.buttondown.hide()
				header.buttonup.show()
			header.buttonlabel.show()
			header.buttontop.show()
		else:
			header.buttonup.hide()
			header.buttondown.hide()
			header.buttonlabel.hide()
			header.buttontop.hide()
		karambino.redrawstatus = True
	
	def showControls(*args, **kw):
		# because we are triggered by mousein/out, we get more arguments than prescribed by signalset
		state = args[0]
		if state:
			showButton(True)
			header.target.hide()
		else:
			showButton(False)
			header.target.show()

	header.buttondown.clicked.btn[1].signalset('flip',greenButtonClick, header.buttondown)
	header.buttonup.clicked.btn[1].signalset('flip',greenButtonClick, header.buttonup)

	header.buttontop.mousein.signalset('hover',greenButtonHover, True)
	header.buttontop.mouseout.signalset('hover',greenButtonHover, False)

	header.mousein.signalset('hover',showControls, True)
	header.mouseout.signalset('hover',showControls, False)
	# end of Header code.
	#########################

	############################################
	# setting up the Tile List
	# listbox = karambino.tileListClass(parent=header) # moved to the top, to make available 
	
	# doing this here so that it is available to calling functions
	listbox.menu = karambino.textClass('', w=70, h=1, parent=listbox)
	listbox.menu.position(0, -140) # menu's parent - listview - will be moved down. Hence, we put thisway up high
	listbox.menu.data = None # will be the pointer to the tile above which we hover.
	
	def tileMenu(*args, **kw):
		'''
		Shows, rePopulates and Hides the menu that hovers above each tile.
		We are activated by MouseIn/Out, so:
		kw['x'],kw['y'],kw['button'],kw['obj'],kw['direction']
		obj is the tile main obj. direction is "in" or "out"
		'''
		if kw['direction']=='out':
			# hiding the menu.
			listbox.menu.position(0, -40)
		else: #  mouse if over a tile now, draw the menu.
			listbox.menu.move(0, -10, "w","w",kw['obj'], redraw=False)
			listbox.menu.data = listbox.data[kw['obj'].datanum] # pointer to the data
			if listbox.data[kw['obj'].datanum]['extpath']:
				listbox.menu.clippint.show(redraw=False)
				listbox.menu.clippext.tip('External link: '+listbox.data[kw['obj'].datanum]['extpath'])
				listbox.menu.clippint.tip('Internal link: '+listbox.data[kw['obj'].datanum]['intpath'])
			else:
				listbox.menu.clippint.hide(redraw=False)
				listbox.menu.clippext.tip('External link: '+listbox.data[kw['obj'].datanum]['intpath'])
			listbox.menu.clippint.tip('Internal link: '+listbox.data[kw['obj'].datanum]['intpath'])
			# later, here we will put logic to customize the menu per tile's specs.
	# see more stuff added to the menu at the bottom. This is to ensure they are above all elements.
	listbox.mouseout.signalset('tilemenu', tileMenu, obj=None, direction='out')

	def buildtile(parent, tilenum, *args, **kw):
		# you can use globals to get your settings from your main scope into here.
		# the following variables of parent may also help you:
		# parent.width = width of the tileList container.
		# parent.visibletiles
		# parent.tileheight
		#
		# this is going to be tile's background and "arent" for all other elements.
		tile = karambino.textClass('', w=parent.width, h=parent.tileheight, parent=parent)
		tile.logo = karambino.imageClass(karambino.kdehelper.iconPath('unknown', 48), parent=tile)
		tile.logo.move(-8,0,'e','e',redraw=False) # move 4 pixels from NorthWest corner of parent.
		tile.back = karambino.imageClass('images/tile.png', w=parent.width, h=parent.tileheight, parent=tile)
		tile.back.mousein.signalset('tilemenu', tileMenu, obj=tile, direction='in')
		tile.title = karambino.textClass('', w=130, h=25, parent=tile)
		tile.title.text.color(255,255,255, redraw=False)
		tile.title.text.align("LEFT", redraw=False)
		tile.title.move(5,0, 'sw', 'sw', redraw=False)
		return tile

	listbox.buildtile = buildtile
	def populatetile(tile, dataunit, *args, **kw):
		'''
		tile - pointer to tile object.
		dataunit - pointer to data object from which to collect the pieces and populate the tile.
		'''
		tile.title.text(dataunit['shortname'],redraw=False)
		tile.logo.path(dataunit['icon'],redraw=False)
		# you don't need to redraw for the whole function because right after it,
		# we move the tile and redraw anyway.
	listbox.populatetile = populatetile

	data = appConfig.data.get()
	listbox.initialize(listbox, data=data, w=w, h=h, tileheight=th)
	listbox.move(0,0, 'nw', 'sw') # the tile thing goes rught under the header.	
	resizeApplet()
	#
	##########################################

	def urlsdropped(*args, **kw):
		'''
		Just a small fn that points to external fn. Didnt want to bog this place down.
		One idea is to separate the UI-handling code from data handling code for ease of maintaining.
		Note, we are receiving kw with args from itemDropped, which has specific keys: x,y, droptext
		'''
		x, y, text = kw['x'],kw['y'],kw['droptext']
		obj = args[0]
		urls = [a.strip() for a in text.splitlines()] # if one or more URLs dropped, turns it into []
		# note, more than one item can be dropped.
		# Regardless of if 1 or more items are dropped, the fn above will give us a list []
		status('Drop detected, %s' %urls)
		if processUrls(urls): # only takes [], returns True if items were successfully added.
			# this gets all. Will implement view-specific filters.
			listbox.data = sortby(['shortname'], appConfig.data.get())
			listbox.refresh()
			header.buttonlabel.text(appConfig.data.__len__())
			resizeApplet()
	header.itemDropped.signalset('processdrop',urlsdropped, header)

	# finally, finishing the decoration of the menu. This has to be on very top, and thus is the last.
	def menuItemClicked(*args, **kw):
		if kw['action']=='clippext':
			karambino.execute('dcop klipper klipper setClipboardContents "'+kw['obj'].data['extpath']+'"')
		elif kw['action']=='clippint':
			karambino.execute('dcop klipper klipper setClipboardContents "'+kw['obj'].data['intpath']+'"')
		elif kw['action']=='remove':
			appConfig.common['webserver'].remove(kw['obj'].data['serial'])
			appConfig.data.remove(key=kw['obj'].data['key'])
			kw['obj'].parent.data = sortby(['shortname'], appConfig.data.get())
			kw['obj'].parent.refresh() # listbox
			kw['obj'].parent.parent.buttonlabel.text(appConfig.data.__len__()) # header
			resizeApplet()

	##################### TILE MENU part 2 ####################
	listbox.menu.clippext = karambino.imageClass(karambino.kdehelper.iconPath('editcopy', 22), parent=listbox.menu)
	listbox.menu.clippext.move(5, 0, 'w','w')
	listbox.menu.clippext.clicked.btn[1].signalset('click', menuItemClicked, action='clippext', obj=listbox.menu)
	listbox.menu.clippext.logo = karambino.imageClass(karambino.kdehelper.iconPath('knetworkmanager', 16), parent=listbox.menu.clippext)
	listbox.menu.clippext.logo.move(0, 5, 'sw','sw')
	listbox.menu.clippint = karambino.imageClass(karambino.kdehelper.iconPath('editcopy', 22), parent=listbox.menu)
	listbox.menu.clippint.move(32, 0, 'w','w')
	listbox.menu.clippint.clicked.btn[1].signalset('click', menuItemClicked, action='clippint', obj=listbox.menu)
	listbox.menu.clippint.logo = karambino.imageClass(karambino.kdehelper.iconPath('network', 16), parent=listbox.menu.clippint)
	listbox.menu.clippint.logo.move(0, 5, 'sw','sw')
	listbox.menu.remove = karambino.imageClass(karambino.kdehelper.iconPath('stop', 22), parent=listbox.menu)
	listbox.menu.remove.move(59, 0, 'w','w')
	listbox.menu.remove.clicked.btn[1].signalset('click', menuItemClicked, action='remove', obj=listbox.menu)

def supportedFilter(url):
	'''
	We do not support all files. Examples are *.desktop files, folders, etc.
	'''
	global appConfig
	if url[:7]=='file://': url = url[7:]
	banned = [
		lambda(x): bool(re.search('.+\.desktop$',x)),
		lambda(x): bool(re.search('^[\w]+://',x)),
		]
	i = banned.__len__()
	stop = False
	while i and not stop:
		stop = banned[i-1](url)
		i -= 1
	if stop: url = ''
	return url

def processUrls(urls):
	global appConfig
	'''
	Processing the list of urls into manageable parts.
	The result will be a dictionary type with a specific set of keys.
	See dataClass's self.template for the list of keys.
	'''
	#key:'', # must be unique. Usually the URL of the resource.
	#objtype:'',
	#shortname:'',
	#plugin:'',
	#group:0, # open = 1, undecided =0 , blocked =-1
	#tags:[], # maybe migrate set's functionality to this later.
	change = False
	# part 1: taking the path apart.
	if urls.__len__() > 1:
		# this is a list of files. Treat them as a group.
		# at this time I don't know what to do with them, so
		# TODO
		status("processUrls:\n we have a group of files and do not know what to do with them...")
	else:
		# there is only 1 item in the list of urls.
		url = urls[0]
		# we get a [], either empty, or with reference to data object.
		if not appConfig.data.get(key=[url]): # i.e. if the item does not exist.
			# some things we do not process. *.desktop files are one example.
			url = supportedFilter(url)
			if not url:
				status('processUrls:\n Hmmm... Nothing to process')
			else:
				# localpath = url.split('://',1)[1] # when check for blah:// leaves supportedFilter, put this back.
				a = appConfig.common['webserver'].add(url, url.rsplit('/',1)[-1],customhost=appConfig.common['extIP'])
				item = {
					'key':url,
					'shortname':url.rsplit('/',1)[-1], # just the file name.
					'icon':karambino.kdehelper.iconPath(karambino.kdehelper.iconForMime(mimetypes.guess_type(url)[0]), 48),
					'extpath':a['externalpath'],
					'intpath':a['internalpath'],
					'serial':a['serial'],
					}
				appConfig.data.add(**item) # this uppacks them into keyed arguments
				change = True
				status('item %s added'%url)
	return change

##################################################################
# Testing & helper functions.
#
def notificationWindow(message, title="", mode=None):
	'''
	This uses KDE kdialog app and asks for main body message and window title.
	App name is attached automatically
	'''
	global appConfig
	myCommand = 'kdialog --caption "'+appConfig.common['name']+'" --title "'+title+'" --msgbox "'+message+'"'
	karambino.execute(myCommand)

def status(*args, **kw):
	# i will assume that if there is anything in the args, it's one string.
	# which we just show.
	# else, unpack **kw and look for message, title etc.
	try:
		print "Horovod: ", args[0]
	except:
		# will add fancier processing here later.
		print "Horovod: ", kw['message']

def ini2dict(text):
	'''
	Very basic ini-style text parser. Only treats '=' as key, value separators.
	Takes either a list type of separate lines, or one string/unicode type
	which it splits and strips.
	Returns a dict object with converted contents of the text.
	'''
	#text = '[version]\nwebserver=12334\n\n[other section]\nkey=value\n'
	if not type(text)==type([]):
		text = [ i.strip() for i in text.splitlines() if i] # throwing away empty lines, removing extra spaces.
	root = {}
	active = root # if no sections are found, all key, value pairs are assigned to root.
	for i in text:
		if i[0]=='[' and i[len(i)-1]==']':
			# section name
			i = i.strip('[]')
			root[i]={}
			active = root[i]
		else:
			# key, value pair
			try:
				key, value = i.split('=',1)
			except:
				key, value = i, ''
			active[key]=value
	return root
#
##################################################################

def unpackParts(widget):
	homepath = os.environ['HOME']+'/.superkaramba/horovod'
	if not os.path.exists(homepath):
		os.makedirs(homepath)
	try:
		userver = int(ini2dict(open(homepath+'/plugins/plugins.ini').read())['version']['webserver'])
	except:
		userver = 0
	try:
		pkgver = int(ini2dict(karambino.readThemeFile(widget, 'plugins/plugins.ini'))['version']['webserver'])
	except:
		pkgver = 1
	print "installed plugins ver is %s, packaged is %s" % (userver, pkgver)
	if userver < pkgver:
		from zipfile import is_zipfile
		themepath = karambino.getThemePath(widget)
		if is_zipfile(themepath):
			print "unpacked already, copying."
			status("SKZ detected, extracting")
			toExtract = []
			myCommand = ['unzip', '-o', themepath]
			myCommand.append('plugins/webserver/*')
			myCommand.append('plugins/plugins.ini')
			myCommand.append('-d')
			myCommand.append(homepath)
			os.spawnvp(os.P_WAIT, 'unzip', myCommand) # this sommand holds the progress until extracting is done.
		else: # this means we do NOT run from SKZ file
			print "unpacked already, copying."
			myCommand = ['cp', '-r', themepath+'/plugins/', '-t', homepath]
			os.spawnvp(os.P_WAIT, 'cp', myCommand)

def upnpclient(intIP, gateway, port):
	global appConfig
	import plugins.upnpclient
	extIP = ''
	portset = False
	status = 200
	if plugins.upnpclient.client.activate(intIP, gateway):
		extIP = plugins.upnpclient.client.getExtIP(refresh=True) # otherwise we use cached value.
		# the part bellow increments the port number if busy and tries again.
		while (not portset) and status==200 and port<64000: # if status is not 200=OK, router is crazy.. no sence talking to it in a normal fasion.
			data, status = plugins.upnpclient.client.testPort(port)
			try:
				porttaken = bool(data['enabled']==1 and data['intIP']!=intIP and status==200)
			except:
				porttaken = False
			if porttaken:
				# this port is forwarding to some other computer
				port += 1 # will take a look at next port.
			elif status!=200: # Port is NOT taken.
				# regardless of the outcomes, we force this onto the router.
				status = plugins.upnpclient.client.addMapping(port)
				portset = True
			else: # the only remaining choice = port is assigned to us already.
				portset = True
	return ({'extIP':extIP,'port':port}, bool(status==200)) # converting status into True if 200.

def startServer():
	global appConfig
	success = False
	import plugins.nmclient
	intIP, gateway = plugins.nmclient.getIP()
	extIP = intIP # until proven othervise.
	port = 8080
	if intIP and gateway:
		if plugins.nmclient.natIP(intIP): # i.e. the IP is from internal network
			data, status = upnpclient(intIP, gateway, port) # 8080 is the port. Implement a diff way to proscribing the starting port.
			if status:
				extIP = data['extIP']
				port = data['port']
		import plugins.webclient
		appConfig.common['webserver'] = plugins.webclient.client()
		appConfig.common['webserver'].start(intIP, port)
		success = True
	print "External IP is %s, machine's IP is %s and server's port is %s" %(extIP, intIP, port)
	appConfig.common['intIP'] = intIP
	appConfig.common['extIP'] = extIP
	appConfig.common['port'] = port
	return success

##################################################################
# KARAMBA's built-in functions
#
#this is called when you widget is initialized
def initWidget(widget):
	global appConfig
	appConfig=configClass()
	#print appConfig.helper.getIconPath(appConfig.helper.getIconForMime(mimetypes.guess_type('file:///home/dd/tile.txt')[0]))
	appConfig.common['cleanStart'] = True
	# web server set up.
	unpackParts(widget)
	setupObjects(widget)
	success = startServer()
	# if not success:
	#    burn this boat.
	karambino.redrawWidget(widget)

def widgetUpdated(widget):
	global appConfig
	if appConfig.common['cleanStart']:
		karambino.changeInterval(widget, appConfig.common['interval'])

def widgetClosed(widget):
	global appConfig
	try:
		a = karambino.getcfg('threadfarm')
		a.stop()
	except:
		pass
	appConfig.common['webserver'].stop()

# NOTE you must use acceptDrops() to make this work
# but if you are using Karambino, it will do it for you if you set a signal to obj.itemDropped()
def itemDropped(widget, dropText, x=0, y=0):
	karambino.itemDropped(widget, dropText, x, y)

#  botton = button clicked:
#                    1 = Left Mouse Button
#                    2 = Middle Mouse Button
#                    3 = Right Mouse Button
#                    4,5 = Scroll wheel up and down
def widgetClicked(widget, x, y, button):
	karambino.widgetClicked(widget, x, y, button)

# NOTE you must use attachClickArea() to make a meterClicked work
# but if you are using Karambino, it will do it for you if you set a signal to obj.clicked()
def meterClicked(widget, meter, button):
	karambino.meterClicked(widget, meter, button)

def keyPressed(widget, meter, char):
	karambino.keyPressed(widget, meter, char)

def themeNotify(widget, caller, message):
	pass
	
def widgetMouseMoved(widget, x, y, button):
	#Warning:  Don't do anything too intensive here
	karambino.widgetMouseMoved(widget, x, y, button)

# This will be printed when the widget loads.
print "Horovod is loaded."